<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .v-scroll {
            width: 300px;
            height: 400px;
            border: 1px solid black;
            overflow-y: scroll;
            margin: 100px 0 0 100px;
        }

        li {
            list-style: none;
            padding-left: 20px;
            line-height: 40px;
            height: 40px;
            box-sizing: border-box;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="v-scroll" @scroll="doScroll" ref="scrollBox">
            <ul :style="blankStyle" style="height: 100%">
                <li v-for="item in currentList" :key="item.id">
                    {{ item }}
                </li>
            </ul>
        </div>
        <button @click="getAllList" >追加300</button>
    </div>


    <script>
        const { createApp, ref, onMounted, computed } = Vue

        <!-- 创建一个应用实例 -->
        createApp({
            // setup 函数用于配置和初始化组件的状态
            setup() {
                // allList 用于存储列表的所有项目
                const allList = ref([]);

                // 初始化时获取全部列表项，参数为列表项的数量
                getAllList(300); 

                /**
                 * 获取全部列表项
                 * @param count 列表项的数量
                 */
                function getAllList(count) {
                    const length = allList.value.length;
                    // 循环创建列表项，并添加到 allList 中
                    for (let i = 0; i < count; i++) {
                        allList.value.push(`我是列表${length + i + 1}项`)
                    }
                }

                // scrollBox 用于存储滚动框的引用
                const scrollBox = ref(null);

                // boxHeight 用于存储滚动框的高度
                const boxHeight = ref(0);

                /**
                 * 获取滚动框的高度
                 */
                function getScrollBoxHeight() {
                    boxHeight.value = scrollBox.value.clientHeight;
                }

                // 组件挂载时，初始化滚动框高度，并监听窗口大小改变事件
                onMounted(() => {
                    getScrollBoxHeight();
                    window.onresize = getScrollBoxHeight;
                    window.onorientationchange = getScrollBoxHeight;
                })

                // itemHeight 用于存储列表项的高度
                const itemHiehgt = ref(40);

                /**
                 * 计算可见列表项的数量
                 * @returns 可见列表项的数量
                 */
                const itemNum = computed(() => {
                    return ~~(boxHeight.value / itemHiehgt.value) + 2;
                });

                // startIndex 用于存储当前显示的列表项的起始索引
                const startIndex = ref(0);

                /**
                 * 处理滚动事件，计算当前显示的列表项的起始索引
                 */
                const doScroll = _.throttle(() => {
                    const index = ~~(scrollBox.value.scrollTop / itemHiehgt.value);
                    if (index === startIndex.value) return;
                    startIndex.value = index;
                }, 200)

                // endIndex 用于存储当前显示的列表项的结束索引
                const endIndex = computed(() => {
                    let index = startIndex.value + itemNum.value * 2;
                    if (!allList.value[index]) {
                        index = allList.value.length - 1;
                    }
                    return index;
                });

                /**
                 * 计算当前应该显示的列表项
                 * @returns 当前应该显示的列表项数组
                 */
                const currentList = computed(() => {
                    let index = 0;
                    if (startIndex.value <= itemNum.value) {
                        index = 0;
                    } else {
                        index = startIndex.value - itemNum.value;
                    }
                    return allList.value.slice(index, endIndex.value + 1);
                });

                // blankStyle 用于存储空白区域的样式
                const blankStyle = computed(() => {
                    let index = 0;
                    if (startIndex.value <= itemNum.value) {
                        index = 0;
                    } else {
                        index = startIndex.value - itemNum.value;
                    }
                    return {
                        paddingTop: index * itemHiehgt.value + "px", // 设置顶部空白的高度

                        paddingBottom: (allList.value.length - endIndex.value - 1) * itemHiehgt.value + "px" // 设置底部空白的高度           
                    };
                });

                // 返回组件的状态
                return {
                    allList,
                    currentList,
                    boxHeight,
                    itemHiehgt,
                    scrollBox,
                    doScroll,
                    blankStyle
                }
            }
        }).mount('#app')
    </script>
</body>
</html>
